package net.xo68.destiny.starter.worker.logger;

import lombok.extern.slf4j.Slf4j;
import net.xo68.destiny.contract.common.SystemConstants;
import net.xo68.destiny.core.util.DiskUtils;
import net.xo68.destiny.core.util.TimeUtils;
import net.xo68.destiny.starter.worker.SchedulerWorkerProperties;
import org.springframework.stereotype.Component;

import java.io.File;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 任务日志写入
 * @author wuxie
 * @version 2020-10-29
 */
@Slf4j
@Component
public class JobLoggerWriter implements Runnable {

    /**
     * 日志事件队列
     */
    private LinkedBlockingQueue<JobLoggingEvent> eventQueue;
    /**
     * 状态
     */
    private AtomicInteger workState;

    /**
     * 写入缓冲
     */
    private final ConcurrentHashMap<Long,StringBuilder> writeBuffer=new ConcurrentHashMap<>();

    private final SchedulerWorkerProperties schedulerWorkerProperties;

    private final JobLoggerIndex jobLoggerIndex;

    private final JobLoggerFormatter jobLoggerFormatter;


    public JobLoggerWriter(SchedulerWorkerProperties schedulerWorkerProperties,JobLoggerIndex jobLoggerIndex) {
        this.schedulerWorkerProperties = schedulerWorkerProperties;
        this.jobLoggerIndex = jobLoggerIndex;
        this.jobLoggerFormatter=new JobLoggerFormatter(this.schedulerWorkerProperties.getLoggerPattern());
    }

    public LinkedBlockingQueue<JobLoggingEvent> getEventQueue() {
        return eventQueue;
    }

    public void setEventQueue(LinkedBlockingQueue<JobLoggingEvent> eventQueue) {
        this.eventQueue = eventQueue;
    }

    public AtomicInteger getWorkState() {
        return workState;
    }

    public void setWorkState(AtomicInteger workState) {
        this.workState = workState;
    }

    @Override
    public void run() {
        if (this.workState.get()==JobLoggerFactory.WORKER_STATE_STARTED){
            if(this.eventQueue==null || this.eventQueue.isEmpty()){
                return;
            }

            final int maxDrain = 128;
            List<JobLoggingEvent> eventList = new ArrayList<>(maxDrain);
            while (true){
                int numDrained = this.eventQueue.drainTo(eventList, maxDrain);
                if (numDrained == 0)
                    break;
                for (JobLoggingEvent loggingEvent: eventList){
                    //写入日志
                    append(loggingEvent);
                }
                eventList.clear();
            }
            flush();
        }
    }

    /**
     * 写入缓冲
     * @param loggingEvent
     */
    private void append(JobLoggingEvent loggingEvent){
        if(!this.writeBuffer.containsKey(loggingEvent.getLoggerIndex())){
            StringBuilder sb=new StringBuilder();
            this.writeBuffer.put(loggingEvent.getLoggerIndex(),sb);
        }
        StringBuilder loggerBuilder=this.writeBuffer.get(loggingEvent.getLoggerIndex());
        loggerBuilder.append(jobLoggerFormatter.format(loggingEvent));
    }

    /**
     * 清空缓冲，进行日志落盘
     */
    private void flush(){
        if(this.writeBuffer.size()>0){
            for (Map.Entry<Long,StringBuilder> entry:this.writeBuffer.entrySet()){
                try {
                    Long dayIndex=jobLoggerIndex.getDayIndex(entry.getKey());
                    LocalDateTime localDateTime=TimeUtils.ofLocalDateTime(dayIndex*TimeUtils.DAY_MILLI);
                    String folder=TimeUtils.toString(localDateTime,"yyyy-MM-dd");
                    File loggerFolder=new File(this.schedulerWorkerProperties.loggerPath(),folder);
                    if(!loggerFolder.exists()){
                        loggerFolder.mkdirs();
                    }
                    File loggerFile=new File(loggerFolder,
                            String.valueOf(entry.getKey())+this.schedulerWorkerProperties.getLoggerSuffix());
                    DiskUtils.writeFile(loggerFile,entry.getValue().toString().getBytes(SystemConstants.ENCODE_DEFAULT),true);
                }catch (Exception e){
                    log.error(e.getMessage(),e);
                }
            }
            this.writeBuffer.clear();
        }
    }
}
